﻿using System;
using System.Collections.ObjectModel;
using System.Device.Location;
using System.Windows;
using System.Windows.Media;

using Microsoft.Phone.Controls.Maps;
using Microsoft.Phone.Controls.Maps.Core;

using Coding4Fun.WheresMyCar.RouteService;
using Microsoft.Phone.Controls.Maps.Platform;
using Microsoft.Phone.Net.NetworkInformation;

namespace Coding4Fun.WheresMyCar
{
    public class RouteHelper
    {
        public static string MAP_CREDENTIALS = "USE BING MAPS API KEY!!!!!";
        const string RouteName = "_customRouteLayer";
        private MapBase _map;
        WheresTheCarSettings _settings;

        public bool RoutePending { get; set; }

        // bearings	
        private const double due_north = 0, due_south = 180, due_east = 90, due_west = 270;

        public RouteHelper(MapBase map, WheresTheCarSettings settings)
        {
            _settings = settings;
            _map = map;
            RoutePending = false;
        }

        #region Routing
        //http://msdn.microsoft.com/en-us/library/ee681887.aspx
        // This method makes the initial CalculateRoute asynchronous request using the results of the Geocode Service.
        public void CalculateRouteToCar(GeoCoordinate currentLocation)
        {
            if( !NetworkInterface.GetIsNetworkAvailable() || RoutePending == true || _map == null || 
                Utility.IsCoordNullOrUnknown(_settings.ParkingSpot) || Utility.IsCoordNullOrUnknown(currentLocation))
                return;

            // Create the service variable and set the callback method using the CalculateRouteCompleted property.
            var routeService = new RouteServiceClient("BasicHttpBinding_IRouteService");
            routeService.CalculateRouteCompleted += routeService_CalculateRouteCompleted;

            // Set the token.
            var routeRequest = new RouteRequest
            {
                Credentials = new Credentials { ApplicationId = MAP_CREDENTIALS },
                Options = new RouteOptions
                {
                    Mode = TravelMode.Walking,
                    Optimization = RouteOptimization.MinimizeDistance,
                    RoutePathType = RoutePathType.Points
                }
            };

            // Return the route points so the route can be drawn.

            // Set the waypoints of the route to be calculated using the Geocode Service results stored in the geocodeResults variable.
            double alt1 = currentLocation.Altitude;
            if (double.IsNaN(alt1)) alt1 = 0;
            double alt2 = currentLocation.Altitude;
            if (double.IsNaN(alt2)) alt2 = 0;

            routeRequest.Waypoints = new ObservableCollection<Waypoint>()
		            {
		                new Waypoint { Description = "Start", Location = 
		                    new Location { Altitude = alt1, Latitude = currentLocation.Latitude, Longitude = currentLocation.Longitude }},
		                new Waypoint { Description = "Car", Location = 
		                    new Location { Altitude = alt2, Latitude = _settings.ParkingSpot.Latitude, Longitude = _settings.ParkingSpot.Longitude }}
		            };

            // Make the CalculateRoute asnychronous request.
            RoutePending = true;
            routeService.CalculateRouteAsync(routeRequest, routeRequest.Waypoints);
        }

        void routeService_CalculateRouteCompleted(object sender, RouteService.CalculateRouteCompletedEventArgs e)
        {
            try
            {
                Utility.LastRouteResult = null;

                // If the route calculate was a success and contains a route, then draw the route on the map.
                if (e.Error == null)
                {
                    Utility.LastRouteResult = e.Result.Result;
                    _settings.DistanceToCar = e.Result.Result.Summary.Distance * 1000.0; // Conversions between metric/imperial assumes meters

                    // Set properties of the route line you want to draw.
                    var routeLine = new MapPolyline
                    {
                        Locations = new LocationCollection(),
                        Stroke = new SolidColorBrush((Color)_map.Resources["PhoneAccentColor"]),
                        Opacity = 0.8,
                        StrokeThickness = 5.0
                    };

                    // Retrieve the route points that define the shape of the route.
                    foreach (var p in e.Result.Result.RoutePath.Points)
                    {
                        routeLine.Locations.Add(new GeoCoordinate { Altitude = p.Altitude, Latitude = p.Latitude, Longitude = p.Longitude });
                    }

                    // Add a map layer in which to draw the route.
                    var myRouteLayer = new MapLayer { Name = RouteName };
                    // Add the route line to the new layer.
                    myRouteLayer.Children.Add(routeLine);

                    // Set the map view using the rectangle which bounds the rendered route.
                    _map.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        RemoveRoute(_map);
                        _map.Children.Add(myRouteLayer);
                        _map.SetView(Utility.ConvertBoundingToRect(e.Result.Result.Summary.BoundingRectangle));
                    }));
                }
                else
                {
                    MessageBox.Show("Sorry, but a route couldn't be calculated\n" + e.Error.Message);

                    // Clear the route
                    _map.Dispatcher.BeginInvoke(new Action(() => RemoveRoute(_map)));
                }
            }
            finally
            {
                RoutePending = false;
            }
        }

        private static void RemoveRoute(MapBase map)
        {
            var child = (UIElement)map.FindName(RouteName);
            if (child != null) map.Children.Remove(child);
        }

        #endregion

        #region Cartesian
        protected LocationRect GetBoundingBox(double lat_degrees, double lon_degrees, double distance_in_miles)
        {

            var radius = MercatorUtility.EarthRadiusInMeters;
	
		    // convert latitude and longitude into radians 
            var lat_r = MercatorUtility.DegreesToRadians(lat_degrees);
            var lon_r = MercatorUtility.DegreesToRadians(lon_degrees);
			
		    // find the northmost, southmost, eastmost and westmost corners distance_in_miles away
		    // original formula from
		    // http://www.movable-type.co.uk/scripts/latlong.html
	
		    var northmost  = Math.Asin(Math.Sin(lat_r) * Math.Cos(distance_in_miles/radius) + Math.Cos(lat_r) * Math.Sin(distance_in_miles/radius) * Math.Cos(due_north));
		    var southmost  = Math.Asin(Math.Sin(lat_r) * Math.Cos(distance_in_miles/radius) + Math.Cos(lat_r) * Math.Sin(distance_in_miles/radius) * Math.Cos(due_south));
		
		    var eastmost = lon_r + Math.Atan2(Math.Sin(due_east)* Math.Sin(distance_in_miles/radius)*Math.Cos(lat_r),Math.Cos(distance_in_miles/radius)- Math.Sin(lat_r)* Math.Sin(lat_r));
		    var westmost = lon_r + Math.Atan2(Math.Sin(due_west)* Math.Sin(distance_in_miles/radius)*Math.Cos(lat_r),Math.Cos(distance_in_miles/radius)- Math.Sin(lat_r)* Math.Sin(lat_r));
			
		    northmost = ConvertRadiansToDegrees(northmost);
		    southmost = ConvertRadiansToDegrees(southmost);
		    eastmost = ConvertRadiansToDegrees(eastmost);
		    westmost = ConvertRadiansToDegrees(westmost);

            double lat1, lat2, lon1, lon2;

		    // sort the lat and long so that we can use them for a between query		
		    if (northmost > southmost) { 
			    lat1 = southmost;
			    lat2 = northmost;
		
		    } else {
			    lat1 = northmost;
			    lat2 = southmost;
		    }
	
		    if (eastmost > westmost) { 
			    lon1 = westmost;
			    lon2 = eastmost;
		
		    } else {
			    lon1 = eastmost;
			    lon2 = westmost;
		    }

            return null;
            //return array(lat1,lat2,lon1,lon2);
        }

        public static double ConvertRadiansToDegrees(double radians)
        {
            double degrees = (180 / Math.PI) * radians;
            return (degrees);
        }
        
        #endregion
    }
}
